﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using BeerScanatronUI.ScanatronService;
using System.ServiceModel;
using System.IO;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.Security.Principal;
using System.Reflection;
using System.Windows.Forms;

namespace BeerScanatronUI
{
    public class Server : IServer
    {
        IServiceProvider _sp;

        /// <summary>
        /// Server plumbing: Binding
        /// </summary>
        private BasicHttpBinding _binding;

        /// <summary>
        /// Server plumbing: Address to services.
        /// </summary>
        private EndpointAddress _address;

        /// <summary>
        ///  Is true if server is connected and ready for operations.
        /// </summary>
        private bool _isServerPresent = false;

        public Server(IServiceContainer serviceContainer, BasicHttpBinding binding, EndpointAddress address)
        {
            _sp = (IServiceProvider)serviceContainer;
            _binding = binding;
            _address = address;
        }

        /// <summary>
        /// Helper - Throw exceptions if IServer data method called and the server is not present.
        /// </summary>
        private void ServerPresentTest()
        {
            if (!_isServerPresent)
            {
                throw new InvalidOperationException(Consts.Strings.ServerNotPresent);
            }
        }

        /// <summary>
        /// IServer consumer must call this before any data IO methods.
        /// Return true if server is connected and ready for operations.
        /// </summary>
        /// <returns></returns>
        bool IServer.IsServerPresent()
        {
            try
            {
                using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
                {
                    // Do simple request and see if it work.  If so, server is present.
                    int limit = client.GetMaximumBeersPerDay();
                    _isServerPresent = true;
                }
            }
            catch (Exception ex)
            {
                // Server not connected.
                _isServerPresent = false;

                // Create error message.
                string s = ex.Message;
                Exception innerEx = ex.InnerException;
                while (innerEx != null)
                {
                    s += "\n\nInner exception: " + innerEx.Message;
                    innerEx = innerEx.InnerException;
                }
                MessageBox.Show(s, "Network or server error");

                // Write to event log if we are running as administrator.
                WindowsIdentity winId = WindowsIdentity.GetCurrent();
                WindowsPrincipal winPrin = new WindowsPrincipal(winId);
                if (winPrin.IsInRole(WindowsBuiltInRole.Administrator))
                {
                    string sourceName = Assembly.GetExecutingAssembly().GetName().Name;
                    if (!EventLog.SourceExists(sourceName))
                    {
                        EventLog.CreateEventSource(sourceName, "Application");
                    }
                    EventLog.WriteEntry(sourceName, s, EventLogEntryType.Error);
                }
            }

            // Return state.
            return _isServerPresent;
        }

        /// <summary>
        /// Return true if bar code is already in database.
        /// </summary>
        /// <param name="barCode"></param>
        /// <returns>True on error or if barcode in use.</returns>
        bool IServer.IsDrinkerBarCodeInUse(string barCode)
        {
            ServerPresentTest();
            bool isUsed = true;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Drinker drinker = client.GetDrinkers().Where(t => t.BarCode == barCode).FirstOrDefault();
                if (drinker == null)
                {
                    isUsed = false;
                }
            }

            return isUsed;
        }

        /// <summary>
        /// Return true if bar code is already in database.
        /// </summary>
        /// <param name="barCode"></param>
        /// <returns>True on error or if barcode in use.</returns>
        bool IServer.IsBeerBarCodeInUse(string barCode)
        {
            ServerPresentTest();
            bool isUsed = true;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Beer beer = client.GetBeers().Where(t => t.BarCode == barCode).FirstOrDefault();
                if (beer == null)
                {
                    isUsed = false;
                }
            }

            return isUsed;
        }

        /// <summary>
        /// Get daily beverage limit.
        /// </summary>
        /// <returns>Zero on error. Otherwise limit.</returns>
        int IServer.GetBeverageDailyLimit()
        {
            ServerPresentTest();
            int limit = 0;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                limit = client.GetMaximumBeersPerDay();
            }

            return limit;
        }

        /// <summary>
        /// Get maximum value for daily beverage limit.
        /// </summary>
        /// <returns>Zero on error. Otherwise limit.</returns>
        int IServer.GetMaximumBeersPerDay_UpperLimit()
        {
            ServerPresentTest();
            int limit = 0;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                limit = client.GetMaximumBeersPerDay_UpperLimit();
            }

            return limit;
        }

        /// <summary>
        /// Set the maxiumum number of beers a drinker may consume per day.
        /// </summary>
        /// <param name="limit">From 1 to GetMaximumBeersPerDay_UpperLimit()</param>
        void IServer.SetMaximumBeersPerDay(int limit)
        {
            ServerPresentTest();
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                // Ensure the value within limits.
                if (limit < 0)
                {
                    // Error - ignore it.
                    return;
                }
                else if (limit > client.GetMaximumBeersPerDay_UpperLimit())
                {
                    // Error - ignore it.
                    return;
                }

                // Value within range.
                client.SetMaximumBeersPerDay(limit);
            }
        }

        /// <summary>
        /// Get image for user with barCode.
        /// </summary>
        /// <param name="badgeID"></param>
        /// <returns>Null on error. Otherwise bitmap image.</returns>
        Image IServer.GetUserImage(string barCode)
        {
            ServerPresentTest();

            // No data.  Return null.
            Image image = null;
            if (string.IsNullOrWhiteSpace(barCode))
            {
                return image;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Drinker drinker = client.GetDrinkers().Where(t => t.BarCode == barCode).FirstOrDefault();
                if (drinker == null)
                {
                    return image;
                }

                DrinkerPicture pic = client.GetPicture(drinker);
                if (pic == null)
                {
                    return image;
                }

                // Convert byte[] to bitmap.
                using (var ms = new MemoryStream(pic.Picture))
                {
                    image = new Bitmap(ms);
                }
            }

            return image;
        }

        /// <summary>
        /// Update image for user with barCode.
        /// </summary>
        /// <param name="badgeID"></param>
        /// <returns>Null on error. Otherwise bitmap image.</returns>
        void IServer.AddUpdateUserImage(string drinkerBarCode, Image image)
        {
            ServerPresentTest();

            // Remove any trailing "a"
            string bc = drinkerBarCode.Trim();
            while (bc.EndsWith("a"))
            {
                bc = bc.Substring(0, bc.Length - 1);
            }

            // No data - exit.
            if (string.IsNullOrWhiteSpace(bc) || image == null)
            {
                return;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Drinker drinker = client.GetDrinkers().Where(t => t.BarCode == bc).FirstOrDefault();
                if (drinker == null)
                {
                    return;
                }

                // Create byte[] from image.
                Byte[] byteArray = null;
                using (Bitmap bm = new Bitmap(image))
                using (MemoryStream ms = new MemoryStream())
                {
                    bm.Save(ms, ImageFormat.Bmp);
                    byteArray = ms.ToArray();
                }

                // Save new image.            
                DrinkerPicture drinkerPic =
                    new DrinkerPicture() { DrinkerID = drinker.Id, Picture = byteArray };
                client.AddUpdatePictures(drinkerPic);
            }
        }

        /// <summary>
        /// Get drinker with barCode.
        /// </summary>
        /// <param name="barCode"></param>
        /// <returns>Null on error. Otherwise Drinker object.</returns>
        Drinker IServer.GetDrinker(string barCode)
        {
            ServerPresentTest();

            // No data.  Return null.
            Drinker drinker = null;
            if (string.IsNullOrWhiteSpace(barCode))
            {
                return drinker;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                drinker = client.GetDrinkers().Where(t => t.BarCode == barCode).FirstOrDefault();
                if (drinker == null)
                {
                    return null;
                }
            }

            return drinker;
        }

        /// <summary>
        /// Get all drinkers.
        /// </summary>
        /// <returns>Null on error. Otherwise Drinker objects.</returns>
        IList<Drinker> IServer.GetAllDrinkers()
        {
            ServerPresentTest();

            // No data.  Return null.
            IList<Drinker> drinkers = new List<Drinker>();

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                IList<Drinker> d = client.GetDrinkers().ToList();
                if (d == null || d.Count() == 0)
                {
                    return drinkers;
                }

                drinkers = d;
            }

            return drinkers;
        }

        /// <summary>
        /// Get number of beers drinker has had.
        /// </summary>
        /// <param name="drinkerBarCode"></param>
        /// <param name="date"></param>
        /// <returns>Number of beers or Consts.UnknownDrinker</returns>
        int IServer.GetDrinkerBeersConsumedToday(string drinkerBarCode)
        {
            ServerPresentTest();
            IServer server = (IServer)this;

            Drinker drinker = server.GetDrinker(drinkerBarCode);
            if (drinker == null)
            {
                return Consts.UnknownDrinker;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                return client.GetDrinksServed(drinker, DateTime.Now);
            }
        }

        /// <summary>
        /// Delete all Dispense information for all drinkers for today's date.
        /// </summary>
        void IServer.DeleteDispenseInfoForToday()
        {
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.DeleteDispensorsByDate(DateTime.Now);
            }
        }

        /// <summary>
        /// Update name and can serve flag for supplied bar code.
        /// </summary>
        /// <param name="drinker"></param>
        void IServer.UpdateDrinker(Drinker drinker)
        {
            ServerPresentTest();
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.UpdateDrinkers(new Drinker[] { drinker });
            }
        }

        /// <summary>
        /// Update beer information for supplied bar code.
        /// </summary>
        /// <param name="beer"></param>
        void IServer.UpdateBeer(Beer beer)
        {
            ServerPresentTest();
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.UpdateBeers(new Beer[] { beer });
            }
        }

        /// <summary>
        /// Add new drinker.
        /// </summary>
        /// <param name="drinker"></param>
        void IServer.AddDrinker(Drinker drinker)
        {
            ServerPresentTest();

            if (drinker.BarCode.EndsWith("a"))
            {
                throw new InvalidDataException("BarCode must be integer.");
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.AddDrinker(drinker);
            }
        }

        /// <summary>
        /// Get the next largest drinker barcode.
        /// </summary>
        /// <returns></returns>
        string IServer.GetNextDrinkerBarCode()
        {
            int barcode = Consts.FirstDrinkerBarCode;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Drinker largest = client.GetDrinkers().AsEnumerable().OrderByDescending(d => int.Parse(d.BarCode)).FirstOrDefault();
                if (largest != null)
                {
                    barcode = int.Parse(largest.BarCode) + 1;
                }
            }

            return barcode.ToString(); ;
        }

        /// <summary>
        /// Get the next largest beer barcode.
        /// </summary>
        /// <returns></returns>
        string IServer.GetNextBeerBarCode()
        {
            int barcode = Consts.FirstBeerBarCode;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                Beer largest = client.GetBeers().AsEnumerable().OrderByDescending(d => int.Parse(d.BarCode)).FirstOrDefault();
                if (largest != null)
                {
                    barcode = int.Parse(largest.BarCode) + 1;
                }
            }

            return barcode.ToString(); ;
        }

        /// <summary>
        /// Get beer information for barCode.
        /// </summary>
        /// <param name="beverageID"></param>
        /// <returns></returns>
        Beer IServer.GetBeer(string barCode)
        {
            ServerPresentTest();

            // Remove any trailing "a"
            string bc = barCode.Trim();
            while (bc.EndsWith("a"))
            {
                bc = bc.Substring(0, bc.Length - 1);
            }

            // No data.  Return null.
            Beer beer = null;
            if (string.IsNullOrWhiteSpace(bc))
            {
                return beer;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                beer = client.GetBeers().Where(t => t.BarCode == bc).FirstOrDefault();
                if (beer == null)
                {
                    return beer;
                }
            }

            return beer;
        }

        /// <summary>
        /// Get beer information for BeerId
        /// </summary>
        /// <param name="BeerId"></param>
        /// <returns></returns>
        Beer IServer.GetBeer(int beerId)
        {
            ServerPresentTest();
            // No data.  Return null.
            Beer beer = null;
            if (beerId < 0)
            {
                return beer;
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                beer = client.GetBeers().Where(t => t.Id == beerId).FirstOrDefault();
                if (beer == null)
                {
                    return beer;
                }
            }

            return beer;
        }

        /// <summary>
        /// Add new beer.
        /// </summary>
        /// <param name="beer"></param>
        void IServer.AddBeer(Beer beer)
        {
            ServerPresentTest();

            if (beer.BarCode.EndsWith("a"))
            {
                throw new InvalidDataException("BarCode must be integer.");
            }

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.AddBeer(beer);
            }
        }

        /// <summary>
        /// Drinker has been served.  Record information.
        /// </summary>
        /// <param name="drinkerBarCode"></param>
        /// <param name="beerBarCode"></param>
        void IServer.DispenseBeer(string drinkerBarCode, string beerBarCode)
        {
            ServerPresentTest();
            IServer server = (IServer)this;

            if (string.IsNullOrWhiteSpace(drinkerBarCode) || string.IsNullOrWhiteSpace(beerBarCode))
            {
                Debug.Fail("Drinker and Beer bar codes must not be null.");
                return;
            }

            Drinker drinker = server.GetDrinker(drinkerBarCode);
            Beer beer = server.GetBeer(beerBarCode);
            DispenserInfo info = new DispenserInfo() { DrinkerID = drinker.Id, BeerID = beer.Id, ServeDate = DateTime.Now };

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                client.AddDispensor(info);
            }
        }

        DispenserInfo[] IServer.GetDispensorsByDate(DateTime date)
        {
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                return client.GetDispensorsByDate(date);
            }
        }

        /// <summary>
        /// Get beverages sold today and their counts.
        /// </summary>
        /// <returns></returns>
        IList<KeyValuePair<Beer, int>> IServer.GetBeverageSalesToday()
        {
            ServerPresentTest();
            IServer server = (IServer)this;
            return server.GetBeverageSalesByDate(DateTime.Now);
        }

        /// <summary>
        /// Get top beverages sold today and their counts.
        /// </summary>
        /// <param name="date"></param>
        /// <returns>Null or zero item list if no information exists.  Otherwise top beers and count served.</returns>
        IList<KeyValuePair<Beer, int>> IServer.GetBeverageSalesByDate(DateTime date)
        {
            ServerPresentTest();
            IServer server = (IServer)this;
            IList<KeyValuePair<Beer, int>> results = new List<KeyValuePair<Beer, int>>();

            DispenserInfo[] salesInfo = null;
            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                salesInfo = client.GetDispensorsByDate(date);
            }

            if (salesInfo != null && salesInfo.Count() > 0)
            {
                Dictionary<int, int> beerCount = new Dictionary<int, int>();
                foreach (var item in salesInfo)
                {
                    if (beerCount.ContainsKey(item.BeerID))
                    {
                        beerCount[item.BeerID] += 1;
                    }
                    else
                    {
                        beerCount[item.BeerID] = 1;
                    }
                }

                foreach (var item in beerCount.ToList().OrderBy(t => t.Value))
                {
                    KeyValuePair<Beer, int> beer = new KeyValuePair<Beer, int>(server.GetBeer(item.Key), item.Value);
                    results.Add(beer);
                }
            }

            return results;
        }

        /// <summary>
        /// Get dates on which at least one beverage was served and counts for types and total server.
        /// </summary>
        /// <returns>Null or zero item list if no information exists. Otherwise, list of tuples
        /// (Event date and total number of beverages sold.</returns>
        IList<Tuple<DateTime, int>> IServer.GetEventDates()
        {
            ServerPresentTest();
            IServer server = (IServer)this;
            IList<Tuple<DateTime, int>> results = null;

            using (ScaningServiceClient client = new ScaningServiceClient(_binding, _address))
            {
                results = client.GetDispensors()
                    .GroupBy(t => t.ServeDate.Date)
                    .Select(t => new Tuple<DateTime, int>(t.Key.Date, t.Count()))
                    .ToList();
            }

            return results;
        }
    }
}
